package basic.arch.component.lock.distributed.redis;

import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import basic.arch.component.lock.aop.annotation.Lock.DisstributionLockType;
import basic.arch.component.lock.config.CurrentLockSettings;
import basic.arch.component.lock.distributed.DistributionLockSupport;

/**
 * redis 通过setnx来实现分布式锁，但是还需要一个独立的线程去扫描TO:LOCK开头的键是否设置了超时时间
 * 
 * @author jannal
 *
 */
@Component("RedisDistributionLock")
@SuppressWarnings("all")
public class RedisDistributionLock extends DistributionLockSupport {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisDistributionLock.class);
    @Resource
    protected StringRedisTemplate redisStringTemplate;

    private ScheduledExecutorService service = Executors.newScheduledThreadPool(1);

    public static final String TO_LOCK = "TO:LOCK:";
    
    @Value("${lock.redis.expire}")
    private long expire = 60000;
    @Value("${lock.redis.scan.interval.time}")
    private long redisScanInteralTime = 5L;
    
    //此处可以使用redis事务行不行?，事务中断就返回false，但是可能导致多个线程都同时被打断，又没有其他线程能抢到 TODO??
    @Override
    protected boolean acquire(CurrentLockSettings currentLockSettings) {
        final String key = TO_LOCK + currentLockSettings.getCurrenLockKey();
        final long currenLockValue=System.currentTimeMillis()+expire;
        boolean flag = redisStringTemplate.opsForValue().setIfAbsent(key, String.valueOf(currenLockValue));
        if(flag){
            // 此处的过期时间设置多少合适？ 当任务执行的时间超出过期时间，此时依然会导致问题
            redisStringTemplate.expire(key, expire, TimeUnit.MILLISECONDS);
        }
        return flag;
    }

    @Override
    protected void unlock0(String key) {
      // 如果此处没有删除成功，就只能key等过期
      redisStringTemplate.delete(TO_LOCK+key);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 延迟一秒后 每5秒执行一次
        service.scheduleAtFixedRate(new DeleteKeyForToLock(), 1L, redisScanInteralTime, TimeUnit.SECONDS);
    }

    /**
     * 主要是为了防止setnx执行但是expire没有执行，导致锁无法释放的问题
     * @author jannal
     */
    public class DeleteKeyForToLock implements Runnable {
        @Override
        public void run() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("开始扫描key为{}的键", TO_LOCK+"*");
            }

            try {
                Set keys = redisStringTemplate.keys(TO_LOCK+"*");
                Iterator iterator = keys.iterator();
               // Set deleteKeySet = new HashSet();
                if (iterator.hasNext()) {
                    Object key = iterator.next();
                    String value = (String)redisStringTemplate.opsForValue().get(key);
                    
                    if (StringUtils.isNotBlank(value) && Long.valueOf(value) < System.currentTimeMillis()) {
                        LOGGER.warn("key:{}过期时间出现问题(可能)，必须清理，目前时间为:{},", key, expire);
                            
                        //通过redis监控key 来防止竞争条件(key已经被更新，此时可能删除到最新的key )
                        redisStringTemplate.execute(new SessionCallback<Long>() {
                            @Override
                            public Long execute(RedisOperations operations) throws DataAccessException {
                                operations.watch(key);// 监视key,，如果在事务执行之前这个(或这些)
                                                      // key 被其他命令所改动，那么事务将被打断
                                operations.multi();// 标记事务的开始
                                String value = (String)operations.opsForValue().get(key);
                                if(StringUtils.isNotBlank(value) && Long.valueOf(value) < System.currentTimeMillis()){
                                    operations.delete(key);
                                    List execResult = operations.exec();// 提交事务
                                    // 如果事务执行失败则返回nil，此时需要抛异常. 而空List表示没有返回，但是成功了
                                    if (execResult == null) {
                                        LOGGER.error("{}已经被改变，处理的事务被打断", key);
                                    } else {
                                        LOGGER.info("{}执行完之后的结果{}", key, execResult);
                                    }
                                }
                                return null;
                            }
                        });
                    }
                    
                    
//                   但是此处会存在竞态条件，有可能加锁成功后，设置expire前，锁被清理掉了。 所以不能通过过期时间来解决
//                    https://github.com/adamswanglin/wllock/issues/1
//                    Long expire = redisStringTemplate.getExpire(key, TimeUnit.MILLISECONDS);
//                    if (expire == null || expire <= 0) {
//                        LOGGER.warn("key:{}过期时间出现问题(可能)，必须清理，目前时间为:{},", key, expire);
//                        deleteKeySet.add(key);
//                    }
                }
                //此处也有问题，当在执行删除之前，key已经被更新，此时可能删除到最新的key TODO
//                if (deleteKeySet.size() > 0) {
//                    LOGGER.debug("开始清理key为{}的键", deleteKeySet);
//                    redisStringTemplate.delete(deleteKeySet);
//                }
            } catch (Exception e) {
                LOGGER.warn("清理TO:LOCK发生异常,{},{}", e.getMessage(), e);
            }
        }
    }

    @Override
    public DisstributionLockType getType() {
        return DisstributionLockType.REDIS;
    }

  

  

}
